﻿using System;
using System.IO;
using System.Text;
using System.Diagnostics;
using Csu.Modsim.ModsimModel;
using Csu.Modsim.ModsimIO;
using ASquared.ModelOptimization; 

namespace iwpm.optimization
{
    public class GrandCouleeTestBus : IScenarioSolvable
    {
        public static string[] Delimiter = new string[] { "\t" }; 
        private Model _model;
        private StreamWriter _sw = null;
        private HydropowerTarget[] _left, _right;
        private TwoBusSystem _grid;
        private OptiModel _optiModel;
        private ConditionalGradientSolver _solver;
        private int _tolerance = 1, _maxIterations = 100;
        private bool _errorOccurred = false;
        private string _name, _xyfile, _newxyfile, _leftPowDevsFile, _rightPowDevsFile;
        private double _hydroWeight, _maxPowerFlow; 
        private Stopwatch _sWatch = new Stopwatch(); 

        public bool WriteLogFile = true;

        public int Tolerance
        {
            get
            {
                return _tolerance;
            }
            set
            {
                _tolerance = value; 
            }
        }

        public int MaxIterations
        {
            get
            {
                return _maxIterations;
            }
            set
            {
                _maxIterations = value; 
            }
        }

        public string Name
        {
            get
            {
                return _name;
            }
        }

        public bool ErrorOccurred
        {
            get
            {
                return _errorOccurred;
            }
        }

        public TimeSpan SolutionTime
        {
            get
            {
                return _sWatch.Elapsed; 
            }
        }

        public GrandCouleeTestBus(string name, string xyfile, string newxyfile, string leftPowDevsFile, string rightPowDevsFile, double hydroWeight, double maxPowerFlow)
        {
            _name = name;
            _xyfile = xyfile;
            _newxyfile = newxyfile;
            _leftPowDevsFile = leftPowDevsFile;
            _rightPowDevsFile = rightPowDevsFile;
            _hydroWeight = hydroWeight;
            _maxPowerFlow = maxPowerFlow; 
        }

        private void Setup()
        {
            // Read model 
            _model = new Model();
            _model.OnMessage += new Model.OnMessageDelegate(_model_OnMessage);
            _model.OnModsimError += new Model.OnModsimErrorDelegate(_model_OnModsimError);
            XYFileReader.Read(_model, _xyfile);

            // Write the model file 
            string dir = Path.GetDirectoryName(Path.GetFullPath(_newxyfile));
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);
            XYFileWriter.Write(_model, _newxyfile, XYFileWriter.CompressionLevelTypes.Full, OutputVersionType.LatestVersion);

            // Read the new model file 
            _model = new Model();
            _model.OnMessage += new Model.OnMessageDelegate(_model_OnMessage);
            _model.OnModsimError += new Model.OnModsimErrorDelegate(_model_OnModsimError);
            XYFileReader.Read(_model, _newxyfile);

            // left and right generators (separated by one branch)
            _left = new HydropowerTarget[] { _model.hydro.GetHydroTarget("CHJ_Target") };
            _right = new HydropowerTarget[] { _model.hydro.GetHydroTarget("GCL_GenTarget"), _model.hydro.GetHydroTarget("BNK_PumpTarget"), _model.hydro.GetHydroTarget("BNK_GenTarget") };

            // two-bus system inputs 
            _model.hydro.IterativeTechnique = IterativeSolutionTechnique.SuccApprox;
            _model.hydro.SetHydroLinkCosts(_hydroWeight);
            TwoBusSystemInputs inputs = new TwoBusSystemInputs(_model, _left, _right, _leftPowDevsFile, _rightPowDevsFile, _hydroWeight, _maxPowerFlow);

            // build OptiModel 
            _optiModel = new OptiModel(_model, ObjectiveFormulation.TwoBusSystem, inputs);
            _grid = (TwoBusSystem)_optiModel.objective;
            _model.maxit = _maxIterations;

            // build solver
            _solver = new ConditionalGradientSolver(_optiModel);
            _solver.Converged += new EventHandler(_solver_Converged);
            _solver.Tolerance = _tolerance;
            _solver.MaxIterations = _maxIterations;
        }

        public void Solve()
        {
            _errorOccurred = false;
            try
            {
                Setup(); 

                _sWatch.Reset();
                _sWatch.Start();
                _solver.Solve();
                _sWatch.Stop();
            }
            catch (Exception ex)
            {
                _model.FireOnError(ex.ToString());
            }
        }

        void _solver_Converged(object sender, EventArgs e)
        {
            writeLog(); 
        }

        private void _model_OnMessage(string message)
        {
            Console.WriteLine(message);
        }

        private void _model_OnModsimError(string message)
        {
            _errorOccurred = true;
            Console.WriteLine(message); 
        }

        private void writeLog()
        {
            if (!WriteLogFile) 
                return; 

            if (_sw == null)
            {
                try
                {
                    string dir = Path.GetDirectoryName(Path.GetFullPath(_model.fname)) + @"\"; 
                    string basename = dir + Path.GetFileNameWithoutExtension(_model.fname);
                    _sw = new StreamWriter(basename + "_power.csv");
                    _solver.LogFile = basename + "_CGM.txt";
                    _optiModel.Debug(basename + "_debug.csv");
                    _sw.WriteLine("TimeStep," 
                        + string.Join(",", Array.ConvertAll(_left, 
                            targ => targ.Name + " Energy Production (MWh)," 
                                  + targ.Name + " Energy Target (MWh)")) + "," 
                        + string.Join(",", Array.ConvertAll(_right, 
                            targ => targ.Name + " Energy Production (MWh)," 
                                  + targ.Name + " Energy Target (MWh)"))
                        + ",Left Bus Deviation (MWh),Right Bus Deviation (MWh),Swing Bus Energy (MWh),Max Power Flow (MW),Calculated Power Flow (MW),Slack Variable Left (MWh),Slack Variable Right (MWh)");
                }
                catch (Exception ex)
                {
                    WriteLogFile = false;
                    Console.WriteLine("Exception occurred when attempting to open output power file. Is it open in another application?\n" + ex.ToString());
                    return; 
                }
            }
            StringBuilder s = new StringBuilder(_model.mInfo.CurrentModelTimeStepIndex.ToString() + ",");
            s.Append(string.Join(",", Array.ConvertAll(_left, targ => targ.Energy.ToString() + "," + targ.EnergyTarget.ToString())));
            s.Append(",");
            s.Append(string.Join(",", Array.ConvertAll(_right, targ => targ.Energy.ToString() + "," + targ.EnergyTarget.ToString())));
            s.Append("," + _grid.LeftPowerDeviation.ToString());
            s.Append("," + _grid.RightPowerDeviation.ToString());
            s.Append("," + _grid.SwingBusPower.ToString());
            s.Append("," + _grid.MaxPowerFlow.ToString());
            s.Append("," + _grid.CalculatedPowerFlow.ToString());
            s.Append("," + _grid.SlackLeft.ToString());
            s.Append("," + _grid.SlackRight.ToString());
            _sw.WriteLine(s.ToString());
        }

        public static GrandCouleeTestBus FromString(string s)
        {
            string[] array = s.Split(Delimiter, StringSplitOptions.None);
            if (array.Length < 7) 
                return null; 

            double hydroWeight, maxPowerFlow;
            if (double.TryParse(array[5], out hydroWeight) && double.TryParse(array[6], out maxPowerFlow))
                return new GrandCouleeTestBus(array[0], array[1], array[2], array[3], array[4], hydroWeight, maxPowerFlow);
            else
                return null; 
        }

        public override string ToString()
        {
            string[] s = new string[] { _name, _xyfile, _newxyfile, 
                _leftPowDevsFile, _rightPowDevsFile, 
                _hydroWeight.ToString(), _maxPowerFlow.ToString() };
            return string.Join(Delimiter[0], s); 
        }
    }
}
